package com.base.utils.resolver;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.base.entity.RedisObject;
import com.base.utils.constract.DataBackupFileConstract;

import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Tuple;

/**
 * backup file  resolver
 * @author huwei
 *
 */
@Slf4j
public class BackupFileResolver {
	
	private DataBackupFileConstract constract = new DataBackupFileConstract() {
	};
	
	private String separator = constract.getNewLineSymbol();
	
	public List<RedisObject> decode(String backupFileString){
		List<RedisObject> result = new ArrayList<RedisObject>();
		
		if(Objects.isNull(backupFileString)) {
			return result;
		}
		
		List<String> redisObjectStrList = decodeString(backupFileString);
		log.info("decode data over , redis object have " + redisObjectStrList.size());
		if(redisObjectStrList.isEmpty()) {
			return result; 
		}
		
		return decodeRedisObjectStr(redisObjectStrList);
	}
	
	public String encodeString(String key ,String value) {
		return encode(key, DataBackupFileConstract.TYPE_STRING, value);
	}
	
	public String encodeList(String key ,List<String> list) {
		return encode(key, DataBackupFileConstract.TYPE_LIST, encodeCollectionValue(list));
	}
	
	public String encodeSet(String key ,Set<String> set) {
		return encode(key, DataBackupFileConstract.TYPE_SET, encodeCollectionValue(set));
	}
	
	public String encodeZset(String key ,Set<Tuple> zset) {
		return encode(key, DataBackupFileConstract.TYPE_ZSET, encodeZsetValue(zset));
	}
	
	private String encodeZsetValue(Set<Tuple> zset) {
		if(Objects.isNull(zset)) {
			return null;
		}
		StringBuffer sb = new StringBuffer();
		Iterator<Tuple> iter = zset.iterator();
		while(iter.hasNext()) {
			Tuple tuple = iter.next();
			sb.append(tuple.getElement());
			sb.append(DataBackupFileConstract.ASSIGNMENT_SYMBOLS);
			sb.append(tuple.getScore());
			sb.append(separator);
		}
		return sb.toString();
	}

	public String encodeHash(String key ,Map<String ,String> map) {
		return encode(key, DataBackupFileConstract.TYPE_HASH, encodeHashValue(map));
	}
	
	@SuppressWarnings("unchecked")
	public String encode(RedisObject ro) {
		switch (ro.getRedisType().toUpperCase()) {
		case DataBackupFileConstract.TYPE_STRING:
			return encodeString(ro.getKey(), (String)ro.getData());
		case DataBackupFileConstract.TYPE_HASH:
			return encodeHash(ro.getKey(), (Map<String,String>)ro.getData());
		case DataBackupFileConstract.TYPE_LIST:
			return encodeList(ro.getKey(), (List<String>)ro.getData());
		case DataBackupFileConstract.TYPE_SET:
			return encodeSet(ro.getKey(), (Set<String>)ro.getData());
		case DataBackupFileConstract.TYPE_ZSET:
			return encodeZset(ro.getKey(), (Set<Tuple>)ro.getData());
		}
		return "";
	}
	
	public String encode(String key ,String type ,String value) {
		if(Objects.isNull(key) || "".equals(key.trim())) {
			throw new RuntimeException("key can not is null or '' ,key = " + key);
		}
		
		StringBuffer sb = head(key ,type);
		if(type.toUpperCase().equals(DataBackupFileConstract.TYPE_HASH) || 
				type.toUpperCase().equals(DataBackupFileConstract.TYPE_ZSET) ) {
			setHashValue(sb ,value);
		}
		else {
			setStringValue(sb, value);
		}
		setTail(sb);
		return sb.toString();
	}
	
	private List<RedisObject> decodeRedisObjectStr(List<String> redisObjectStrList) {
		List<RedisObject> result = new ArrayList<RedisObject>();
		for(int i = 0 ,len = redisObjectStrList.size() ;i < len ;++i) {
			RedisObject ro = decodeRedisObjectStr(redisObjectStrList.get(i));
			if(Objects.nonNull(ro)) {
				result.add(ro);
			}
		}
		return result;
	}

	private RedisObject decodeRedisObjectStr(String string) {
		String temp = string;
		String[] content = temp.split(separator);
		if(Objects.isNull(content)) {
			return null;
		}
		
		Map<String ,String> contentMap = new HashMap<String, String>();
		for(int i = 0 ,len = content.length ;i < len ;++i) {
			String tempContent = content[i];
			int assignmentIndex = -1;
			if(tempContent == null || "".equals(tempContent) 
					|| (assignmentIndex =tempContent.indexOf(DataBackupFileConstract.ASSIGNMENT_SYMBOLS)) == -1) {
				continue;
			}
			
			contentMap.put(tempContent.substring(0 ,assignmentIndex)
					,tempContent.substring(assignmentIndex + DataBackupFileConstract.ASSIGNMENT_SYMBOLS.length()));
		}
		
		String type = contentMap.get(DataBackupFileConstract.TYPE_SYMBOLS.toUpperCase());
		type = Objects.isNull(type) ? contentMap.get(DataBackupFileConstract.TYPE_SYMBOLS.toLowerCase()) : type;
		String key = contentMap.get(DataBackupFileConstract.KEY_SYMBOLS.toUpperCase());
		key = Objects.isNull(key) ? contentMap.get(DataBackupFileConstract.KEY_SYMBOLS.toLowerCase()) : key;
		
		if(Objects.isNull(type) || Objects.isNull(key)) {
			return null;
		}
		
		RedisObject ro = new RedisObject();
		ro.setKey(key);
		ro.setRedisType(type.toUpperCase());
		
		String value = contentMap.get(DataBackupFileConstract.VALUE_SYMBOLS);
		
		switch (type.toUpperCase()) {
		case DataBackupFileConstract.TYPE_STRING:
			ro.setData(value);
			break;
		case DataBackupFileConstract.TYPE_LIST:
			ro.setData(decodeListStr(value));
			break;
		case DataBackupFileConstract.TYPE_SET:
			ro.setData(decodeSetStr(value));
			break;
		case DataBackupFileConstract.TYPE_ZSET:
			ro.setData(decodeZStr(contentMap));
			break;
		case DataBackupFileConstract.TYPE_HASH:
			Map<String ,String> hash = contentMap;
			hash.remove(DataBackupFileConstract.TYPE_SYMBOLS);
			hash.remove(DataBackupFileConstract.KEY_SYMBOLS);
			ro.setData(hash);
		}
		
		return ro;
	}
	
	private Set<String> decodeSetStr(String setValue) {
		String[] valueList = setValue.split(DataBackupFileConstract.DATA_SEPARATOR_SYMBOLS);
		Set<String> set = new HashSet<String>();
		if(Objects.isNull(valueList) || valueList.length == 0) {
			set.add(setValue);
		}
		else {
			for(int i = 0 ,len = valueList.length ;i < len ;++i) {
				set.add(valueList[i]);
			}
		}
		return set;
	}

	private Set<Tuple> decodeZStr(Map<String ,String> map) {
		Set<Tuple> set = new LinkedHashSet<Tuple>();
		Map<String ,String> hash = map;
		hash.remove(DataBackupFileConstract.TYPE_SYMBOLS);
		hash.remove(DataBackupFileConstract.KEY_SYMBOLS);
		for(Map.Entry<String, String> entry : hash.entrySet()) {
			try {
				set.add(new Tuple(entry.getKey(), Double.valueOf(entry.getValue())));
			}catch (Exception e) {
				log.info("score string switch double is error !!!");
			}
		}
		return set;
	}

	private List<String> decodeListStr(String listStr) {
		String[] valueList = listStr.split(DataBackupFileConstract.DATA_SEPARATOR_SYMBOLS);
		List<String> list = new ArrayList<String>();
		if(Objects.isNull(valueList) || valueList.length == 0) {
			list.add(listStr);
		}
		else {
			for(int i = 0 ,len = valueList.length ;i < len ;++i) {
				list.add(valueList[i]);
			}
		}
		return list;
	}
	
	private List<String> decodeString(String backupFileString) {
		String temp = backupFileString;
		List<String> result = new ArrayList<String>();
		Pattern pattern = Pattern.compile(regexOfStandardGrammar());
		Matcher matcher = pattern.matcher(temp);
		while(matcher.find()) {
			result.add(matcher.group(1));
		}
		return result;
	}
	
	private String regexOfStandardGrammar() {
		StringBuffer sb = new StringBuffer();
		sb.append(DataBackupFileConstract.START_SYMBOLS);
		sb.append("([\\s\\S]*?)");
		sb.append(DataBackupFileConstract.END_SYMBOLS);
		return sb.toString();
	}

	
	private String encodeHashValue(Map<String, String> map) {
		if(Objects.isNull(map)) {
			return null;
		}
		StringBuffer sb = new StringBuffer();
		for(Map.Entry<String, String> entry : map.entrySet()) {
			sb.append(entry.getKey());
			sb.append(DataBackupFileConstract.ASSIGNMENT_SYMBOLS);
			sb.append(entry.getValue());
			sb.append(separator);
		}
		return sb.toString();
	}
	
	private void setHashValue(StringBuffer sb, String value) {
		sb.append(value);
	}

	private String encodeCollectionValue(Collection<String> collection) {
		if(Objects.isNull(collection)) {
			return null;
		}
		
		StringBuffer sb = new StringBuffer();
		Collection<String> temp = collection;
		Iterator<String> iter = temp.iterator();
		while(iter.hasNext()) {
			sb.append(iter.next()).append(DataBackupFileConstract.DATA_SEPARATOR_SYMBOLS);
		}
		return sb.substring(0, sb.length() - DataBackupFileConstract.DATA_SEPARATOR_SYMBOLS.length());
	}
	
	private void setTail(StringBuffer sb) {
		sb.append(DataBackupFileConstract.END_SYMBOLS).append(separator);
	}

	private void setStringValue(StringBuffer sb ,String value) {
		sb.append(DataBackupFileConstract.VALUE_SYMBOLS);
		sb.append(DataBackupFileConstract.ASSIGNMENT_SYMBOLS);
		sb.append(value).append(separator);
	}
	
	private StringBuffer head(String key ,String type) {
		StringBuffer sb = new StringBuffer();
		sb.append(DataBackupFileConstract.START_SYMBOLS);
		sb.append(separator);
		sb.append(DataBackupFileConstract.TYPE_SYMBOLS);
		sb.append(DataBackupFileConstract.ASSIGNMENT_SYMBOLS);
		sb.append(type.toUpperCase()).append(separator);
		sb.append(DataBackupFileConstract.KEY_SYMBOLS);
		sb.append(DataBackupFileConstract.ASSIGNMENT_SYMBOLS).append(key).append(separator);
		return sb;
	}
}
